3 Flex 記述言語


この章では、スキャナ定義の構成要素を説明し、その使用例を示します。Flex を効率的に使用するためには、定義の個々の要素を完全に理解することが非常に重要です。したがって、初めて Flex を使うユーザには、時間をかけてこの章を読むことをお薦めします。

Flex スキャナ定義のほとんどの要素は必須要素ではありません。全体的な定義フォーマットは以下のようになります。

これらの1つ1つが以下において詳細に説明されます。

3.1 コメント

C のコードが記述できるところにはどこにでもコメントを記述することができます。コメントの書式は、C のコメントの規則に従います。コメントは、記述情報に影響を与えることはありません。C スタイルのコメントは以下のようになります。

これに加えて、Flex では # で始まるコメントも許されます。このようなコメントは lex.yy.c にはコピーされません。この形式のコメントを使うことはお薦めできません。

注:C 以外の言語(例えば Pascal)のコードを生成する Lex が存在します。このような Lex ではコメントの書式はおそらく異なるものでしょう。Flex の場合は C のコードしか生成しません。

3.2 オプションの C コード

プログラマは2つの異なる方法を用いてスキャナの中に直接 C のコードを含めることができます。第1の方法は、「定義、初期 C コード」セクション(最初の %% より前の部分)にコードを含めることです。第2の方法は、「他の C コード」セクション(2番目の %% よりうしろの部分)にコードを含めることです。どちらの場合もコードはそのまま lex.yy.c にコピーされますので、正当なコードでなければなりません。

第1のセクション中の C コードは以下の形式になります。

ここで %{ と %} のペアが、C コード・ブロックの先頭と末尾を示すために使われています。この形式のコードと定義は、「定義、初期 C コード」セクションのどこにでも自由に記述することができます。定義については次の節で説明します。

C のコードが最初のカラムから始まるのでなければ、%{ と %} のペアは必要ありません。しかし普通は、分りやすくするためにこれらを記述しておいた方が良いでしょう。もう1つのポイントは、#ifdef 等のように最左端のカラムから始まらなければならず、かつ、通常はスキャナ記述情報の先頭に置かれる必要のあるものが存在するという点です。このようなものが %{ と %} のペアに囲まれていないと、Flex はそれを定義の一部であると考えるでしょう。これが、常に %{ と %} を使うもう1つの理由です。

最後の(「他の C コード」)セクション内のコードはそのままコピーされます。ここには特別な宣言は必要ありません。

3.3 定義

定義セクションにおいてプログラマは、ある文字のグループに一意な識別子を与え、その識別子がその文字グループに置き換えられるようにすることができます。定義は以下のような形式になります。

definition_name は最初のカラムから始まらなければならず、さもないとその定義は lex.yy.c にそのままコピーされるということに注意してください。以下に一般的な定義をいくつか挙げます。 definition_name はそのグループの一意な識別子でなければなりません。また、definition はルール・セクション(後述)において正当なものであれば何でも構いません。ルール・セクションや(上の例の ALPHANUM の定義において示されるように)別の定義中において使われる場合には、定義は { と } によって囲まれていなければなりません。

Flex と Lex との非常に重要な相違点に、定義を展開する時 Flex はそれを文字どおりに丸括弧 () で囲むのに対して、Lex は囲まないという点があります。これは ^、<<EOF>>、$、/ を定義中に入れることができないということを意味しています。というのは、これらの文字は丸括弧 () で囲まれた部分に入れることができないからです。これについては、***ページの 3.6.1 節「文字」および***ページの 8 章「Flex と Lex」において、より詳細に説明されます。

例えば、

は、以下のようなプログラミング・スタイルを使っている場合の C の関数宣言にマッチするように見えます。 しかし実際にはこれはうまくいきません。というのは、{FUNCTION} が展開されると、 のようになりますが、これは不正だからです。このような種類の問題に関する説明については、***ページの 8.1.1 節「Flex と POSIX」を参照してください。

3.4 %%

2つのパーセント記号が、スキャナ記述情報のルール・セクションの先頭と末尾を示します。すべての Flex 記述情報は、少なくともルール・セクションの先頭を示す %% を含んでいなければなりません。

3.5 ルール

ルールは Flex の心臓部です。ルールを書くことによってプログラマは、スキャナが何を実行するべきであるかを Flex に通知します。

通常、ルールは2つの部分から構成されます。

このうち pattern が何を認識するべきかを定義し、actions がその何かを認識した時に何を実行するべきであるかをスキャナに知らせます。pattern の部分は空白によって区切られます。これは、空白をマッチさせたい場合には、それを引用符で囲う必要があるということを意味しています。

スキャナがマッチするものを2つ以上見つけた場合、以下の2つのルールを使ってどれを受け入れるかを決めます。

  1. 後続コンテキスト(trailing context)も含めて最も長いものを受け入れます。
  2. マッチするものがすべて同じ長さの場合、スキャナ定義中に最初に記述されたものを受け入れます。
actions は、空(コードなし)にするか、もしくは、1つ以上の C の文を含む単一のコード行、{...} または %{...%} で囲まれた1行以上のコード、単一の垂直棒(|)を記述することができます。以下にいくつか例を挙げます。 どの行も複数の文を含むことができます。| は、そのルールにマッチするものが見つかった場合、次に現れるルールのアクション部に記述されているアクションが実行されるべきであることを Flex に通知します。

注:ほとんどのバージョンの Lex は { と } のペアの外部では単一の文しか許しません(例えば上の sayonara ルールは許されません)。また、C 以外の言語をターゲットにしている Lex では { と } のペアは、例えば Pascal の場合の begin と end のように、異なるシンボルに置き換える必要があるかもしれません。

ルールにマッチしなかった入力に対するデフォルトのアクションはそれを stdout に出力することで、マッチしたパターンに対するデフォルトのアクションはそれを破棄することであるという点に注意してください。これは、最も単純な Flex の定義が

であることを意味しています。これは、入力を変更せずそのまま stdout へ出力します。別の単純な例として以下のようなものがあります。 これは入力の中から foobar という文字の並びをすべて取り除き、取り除いた結果を stdout に出力します。

3.6 パターン・セクション

パターン・セクションは、正規表現と呼ばれる仕組みを使って実際のマッチング処理を実行します。正規表現は、文字列、文字、文字集合(クラス)、演算子から構成されています。正規表現を構成する要素については次節以降で説明します。また正規表現自体については***ページの 3,7 節「正規表現」において議論します。

3.6.1 文字

いくつかの文字は Flex にとって特別の意味があり、それらの文字を単独で使ったのでは、それらの文字自体を表すことができません。以下に Flex における特殊文字とその意味を表にして示します。
 

文字  Flex による解釈 
ピリオドは改行(\n)以外の任意の文字を表します。 
 
バックスラッシュはエスケープ文字です。エスケープ・シーケンスは ANSI C のものと同一です。 
 
[]  角括弧 [] は複数の文字を文字クラスにグループ化します。詳細については***ページの 3.6.3 節「Flex における文字のグループ化」を参照してください。 
 
文字クラスの内部では ^ は否定を意味します。詳細については***ページの 3.6.3 節「Flex における文字のグループ化」を参照してください。文字クラスの外部では、それは行の先頭を意味し、ルールの先頭にのみ置くことができます。例を以下に示します。 
 
[^AB]  否定クラスです。 
 
^foo  行の先頭にある foo という文字の並びにのみマッチします。 
 
foo^  この場合 ^ は普通の文字であるとみなされます。このような時には、希望どおりの結果が確実に得られるようにするために、特別な意味を持つ文字の前にバックスラッシュ \ を置くのが良いでしょう。このような文字の並びをエスケープ・シーケンスと呼びます。エスケープ・シーケンスについてはこの節の最後で説明します。 
 
ハイフンは文字クラスの内部において文字の範囲を表します。文字クラスの外部では、ハイフンはそれ自身を表します。詳細については***ページの 3.6.3 節「Flex における文字のグループ化」を参照してください。 
 
{}  大括弧 {} は、定義の参照、複数行のアクションの囲い、ある範囲にわたる繰り返しの定義を行います。例を挙げると、定義 FOO があって、それをルールの中で参照したい場合に {FOO} を使います。 

与えられたパターンのある範囲にわたる繰り返しを定義するには以下のような {repetition list} を使います。 

    %% 
    f{2,5} /* f の2回以上5回以下の繰り返しにマッチ */ 
    f{2,} /* f の2回以上の繰り返しにマッチ */ 
    f{2} /* f の2回の繰り返しにマッチ */ 
この用法の解釈において、 Flex と Lex の間にはいくつかの相違があります。詳細については、***ページの 8.1.1 節「Flex と POSIX」を参照してください。 
 
()  丸括弧 () を使って優先順位を変更することができます。また、定義が展開される時には、その定義は暗黙のうちに丸括弧 () で囲まれることに注意してください。このため Lex と非互換なところがでてきます。この点については、***ページの 8.1.1 節「Flex と POSIX」と***ページの 3.3 節「定義」において説明されています。 
 
""  二重引用符記号は文字列を表します。引用符の内側の文字列だけがマッチの対象になります。したがって、 
    %% 
    "string" 
は "string" にではなく string にマッチします。 
 
スラッシュは後続コンテキスト(trailing context)を設定します。これは、あるパターンのうしろに特定の文字の並びが続く場合のみ、そのパターンを認識したいような状況です。つまりスラッシュは「その先を見よ(lookahead)」というような演算子として機能するということです。例を挙げると、 
 
abcDEF abcDEF を認識します。 
 
abc/DEF  abc のうしろに DEF が続く場合に限り abc を認識します。DEF の部分は、それらの文字があたかもまだ読まれてはいないかのように扱われ、マッチの対象になりません。 
 
注:1つのルールの中では / は1つだけ許されます。つまり、abc/def/hijkl は不正です。 
<>  かぎ括弧 <> はスタート状態を参照します。それは EOF シンボル(<<EOF>>)にも使われます。これに関する完全な説明については、***ページの 3.8 節「スタート状態」と***ページの 5.6 節「ファイルの終端(End-Of-File)ルール」を参照してください。 
 
?+*  ?、+、* は、ある正規表現が現れることのできる回数を設定します。? は0回もしくは1回(その正規表現が現れることは必須ではないということ)を意味します。+ は1回以上を意味します。* は0回以上を意味します。例えば、 
 
a?  0個もしくは1個の a にマッチします。 
 
a+  1個以上の a にマッチします。 
 
a*  0個以上の a にマッチします。 
 
(ABC)+  ABC という文字の並びが1回以上続くものにマッチします。 
 
[abQrS]?  0個もしくは1個の(5つの文字 abQrS から構成される)この文字クラスのメンバにマッチします。文字クラスに関する詳細については、***ページの 3.6.3 節「Flex における文字のグループ化」を参照してください。 
 
{NUM}*  0個以上の NUM にマッチします。ここでの NUM は定義です。定義に関する詳細については***ページの 3.3 節「定義」を参照してください。 
 
OR 演算子、および、特別なアクションを表します。例えば、 
    apples|oranges 
は apples もしくは oranges のいずれかにマッチし、 
    apples     | 
    oranges  printf("fruit!\n"); 
は、apples と oranges の両方に対して同一のアクションを実行します。 
 
ドル記号は行末を意味します。例えば、 
    end$ 
はその直後が行末である場合にのみ end という文字の並びにマッチします。これは、うしろに続くのが行末である場合のみ end にマッチする 
    end/\n 
とまったく同じです。 
 
これらの文字のどれかをその文字自身として表したい場合には、それを引用符で囲むか(後に示すテーブルにて説明される)エスケープ・シーケンスとしてそれを表さなければなりません。

Flex には3種類のエスケープ・シーケンスがあります。バックスラッシュ \ に続けて 8 進数を使うもの、\x に続けて 16 進数を使うもの、\letter という表記法で文字 letter もしくは特別な表示不可の文字を表すものの3つです。C をよく知っている人であれば、これらが ANSI C のエスケープ・シーケンスであることに気がつくことでしょう。数値によるエスケープ・シーケンスは、100 パーセント移植性があるわけではなく、保守を困難にするので、避けるべきです。

以下に、文字の使用に関する要約を示します。この表中では、c が単一の文字を、NNN が 8 進定数を、HH が 16 進定数を表します。
 

Flex における文字
文字  意味  例 
c が演算子でない場合は、文字 c 自体 
"c"  "|" 
改行以外の任意の文字  Un.x 
\b  バックスペース(BS)  \b 
\t  水平タブ(HT)  \t 
\n  改行(NL)  \n 
\v  垂直タブ(VT)  \v 
\f  頁送り(FF)  \f 
\r  キャリッジ・リターン(CR)  \r 
\"  単一引用符  \" 
\\  単一バックスラッシュ  \\ 
\0  NUL 文字  \0 
\c  上記以外の文字 c については文字 c そのものを表す  \* 
\xHH  16 進数 HH を値として持つ文字  \x1B 
\NNN  8 進数 NNN を値として持つ文字  \033 

注:いくつかのバージョンの Lex は \0 を正しく認識もしくはマッチしません。これは、\0 が NUL、つまり C 文字列の終端文字だからです。NUL をマッチの対象にすることは Flex では問題になりませんが、性能には若干影響します。

さらに加えると、^ 演算子と <<EOF>> はルールの先頭にのみ置くことができます。また、これらと $、/ は丸括弧 () の内部に置くことはできません。このことはまた、特定の定義の正当性に影響を及ぼします。というのは、展開される時に定義は文字どおりに丸括弧 () で囲まれるからです。詳細については、***ページの 3.3 節「定義」と***ページの 8.1.1 節「Flex と POSIX」を参照してください。

3.6.2 Flex における文字列

文字列とは(常に、というわけではありませんが)多くの場合引用符によって囲まれる、文字のグループです。エスケープ・シーケンスが使われない限り、文字列は改行や表示不可の文字を含むことはできません。

-i オプション(詳細については***ページの 5.1 節「大文字・小文字を区別しないスキャナ」を参照)を使わない限り、大文字・小文字の区別も含めた字義どおりの文字列に対してマッチが行われます。引用符付きの文字列については、引用符は認識される文字列には含まれません。

例えば、

はすべて正当な文字列であり、最後のものは引用符も含めてマッチされます。Flex においては文字列には引用符は必須ではありません。したがって、キーワードのグループにマッチさせる場合、 のいずれも正当です。

3.6.3 Flex における文字のグループ化

Flex では、文字をグループ化して文字クラスにすることができます。文字クラスは文字のグループを角括弧 [] で囲むことにより作成されます。どのような文字でも正当です(表示不可の文字についてはエスケープ・シーケンスを使います)。また、文字の範囲をハイフン - を使って指定することができます。文字クラスがルールの中で使われている場合には、Flex はそのクラスの任意のメンバとマッチさせ、あたかも単一文字が使われているかのように振舞います。例えば、

において、最初の例は a から z までの任意の単一文字にマッチします。第2の例は A から Z までの任意の文字が0個以上並んだものにマッチします。

否定文字クラスを表す正規表現を書くこともできます。否定文字クラスは(\n も含めて)文字クラスのメンバ以外であれば何にでもマッチします。これをするには、否定すべきクラスの先頭に ^ を置きます(クラスの外部では ^ は異なる意味を持つことに注意してください)。以下に、正当なクラスの例をいくつか挙げます。
 

[abc]  a、b、c にマッチします。 
 
[abc\n]  a、b、c、\n にマッチします。 
 
[a-z]  ASCII 値が a から z までの範囲にある任意の文字にマッチします。これは任意の英小文字にマッチするということです。 
 
[^a-z]  a から z までの範囲にある文字以外の任意の文字にマッチします。 
 
[ABcd]  A、B、c、d にマッチします。 
 
注: Flex、および、いくつかのバージョンの Lex はクラス内における逆方向の範囲を扱うことができません。したがって、 はエラー・メッセージを出力します。逆方向の範囲は使わないでください。

3.7 正規表現

Flex の文字、文字列、クラス、定義、演算子を組み合わせることで、正規表現として知られているものを構築します。(基本単位が数と演算子である)数学表現と同じように、基本的な要素は単純なもの(文字、演算子、文字列、クラス、定義)ですが、それらを組み合わせることでより複雑な表現式を構築することができます。例えば、c は単一文字の正規表現で、c にマッチします。cc は2つの正規表現をつないだものを含む正規表現で、cc にマッチします。c* は、単一文字の正規表現 c と、それに続く演算子 * から構成される正規表現で、これは0個以上の c にマッチします。正規表現の真のパワーは、個々の要素よりもむしろ、それらを組み合わせる方法にあります。

下の表は、Flex で利用可能な正規表現をすべて示したものです。表中において、c は(エスケープ・シーケンスを含む)任意の単一文字を、r は任意の正規表現を、s は文字列を表します。表は、グループ別に編成されており、最も高い優先度を持つものが一番上にあります。

 
Flex における正規表現
正規表現  マッチの対象  例 
特殊文字を除く任意の文字  A または \n 
改行(\n)を除く任意の文字  efg.* 
[s]  クラス s 中にある任意の文字  [efg] 
[^s]  クラス s 中にない任意の文字  [^mnqs] 
r*  0個以上の r  (a|[e-f])* 
r+  1個以上の r  (a|[e-f])+ 
r?  0個または1個の r  (a|[e-f])? 
r{x,y}  x 個以上 y 個以下の r(abc{1,3} は ab のうしろに1個から3個までの c を付加したものに等しい)  abc{1,5} 
"s"  字義どおりの文字列 s  "****" 
\c  (\c が ANSI C において特別な意味を持たない場合)c  \" または \* 
(r)  r - 丸括弧 () はグループ化のためのもの  (Ab|Bb) 
r1r2  r1 のうしろに r2 が続くもの  Aa 
r1|r2  r1 または r2  A|B 
r1/r2  r2 がうしろに続くという条件を満足する r1  abc/123 
行頭  ^abc 
行末  abc$ 
<start>r  スタート状態(start がアクティブな時 r がアクティブ)  <comment>"*/" 
<<EOF>>  ファイルの終端(End-Of-File ルールを参照)  <<EOF>> 

UNIX においてパターン検索が必要な場合は正規表現がよく使われますが、アプリケーションが異なると、正規表現もよく似てはいるもののまったく同一ではないという点に注意してください。例えば、Flex、egrep、Emacs はいずれもパターン検索のテンプレートとして正規表現を使いますが、それぞれが理解する正規表現は少しずつ異なります。特に、Flex では定義が使われますが、egrep や Emacs では使われませんし、egrep や Emacs は単語の先頭と末尾にマッチさせるための \< と \> とを提供していますが、Flex は提供していません。さらに、Emacs はバッファの先頭に対するマッチングや「ファジー」なマッチング等々を行うための特別な \letter シーケンスを他にも数多く提供しています。

3.8 スタート状態

何等かの条件に基づいてパターン・マッチング処理のルールを活性化することが便利な時があります。例えばいくつかのコンピュータ言語では、重複しているスキャン・ルールの曖昧さを取り除くのを支援するためにパース状態を使います。別の例としては、ある特定の入力が見つかったあとでだけ、あるルールを活性化したいという場合があります。このような状況に対処するために Flex はスタート条件あるいはスタート状態と呼ばれる簡単なシステムを提供しています。

3.8.1 スタート状態の説明

スタート状態は、あるルールがアクティブになるのはいつであるかを Flex に通知するブーリアン値のようなものです。スタート状態は定義セクションにおいて(排他的スタート状態の場合)%x 、(包含的スタート状態の場合)%s を使って宣言されます。

start_state_name は一意な名前でなければならない点に注意してください。これは、他のスタート状態や定義が同じ名前を持ってはならないということです。スタート状態は、1つの状態の名前、もしくは、カンマで区切られた複数の状態の名前をかぎ括弧 <> で囲むことによって、ルール・セクションで参照されます。スタート状態の参照はルールの最初になければならず、1つのルール中には1対のかぎ括弧 <> のみ許されます。このことは、 が正当であり、 はすべて不当であることを意味しています。integer についてはそれと同じ名前を持つ定義が存在し、それ以外のものについてはスタート状態の参照の位置が正しくないか、複数の参照が存在するからです。

これまでのところでは、Flex が異なる2種類のスタート状態をサポートしている事実から目をそらしてきました。2つのスタート状態とは包含的スタート状態(%s)と排他的スタート状態(%x)のことです。これら2つの相違点は、排他的スタート状態が活性化された場合はその状態に属するルールだけが活性化されるのに対して、包含的スタート状態の場合はその状態に属するルールとスタート状態への参照を持たないルールの両方が活性化されるという点にあります。この違いを示す例を挙げると、以下のようになります。

これは state1 状態が活性化されている場合は one を two に置き換え、state1 状態が活性化されているか否かにかかわらず three を four に置き換えます。デフォルトのルールにより、その他のテキストは stdout に出力されます。これに対して、 は、state1 状態が活性化されている時は one を two に置き換え、state1 状態が活性化されていない時のみ three を four に置き換えます。デフォルトのルールにより、その他のテキストは stdout に出力されます。

このことは、排他的スタート状態が使われる場合には、マッチされないテキストが stdout に出力されてはならないのであれば、可能なすべての入力にマッチするルールを個々の排他的スタート状態が持たなければならないということを意味しています。包含的スタート状態の場合は、あらゆる状態において有効な、スタート状態への参照を持たないルールを1つ持つ必要があります。

注:排他的スタート状態は POSIX の一部であるにもかかわらず、Lex はこれをサポートしていません。

3.8.2 状態の活性化

スタート状態の名前を知っているだけではあまり役に立ちません。それらがいつ活性化されるのかを制御しなければなりません。活性化は、アクションの中、もしくは、記述情報内の追加的な C コードを記述する領域の中において BEGIN を使うことで実現されます。使い方は以下のとおりです。

例を挙げると、以下のようになります。 これは、Pascal のコメントの先頭部分を見つけると COMMENT 状態に移行し、コンパイラ・オプションを認識するようになります。BEGIN は最初の %% の直後(最初のルールの前)において使うこともでき、この場合は yylex() は常に指定された状態で開始されます。

上の例においては、定義されていない INITIAL という状態があることに注意してください。この状態は常に利用可能で、活性化された状態が1つも存在しない時のスキャナの初期状態を表します。このことは、BEGIN(INITIAL) によってスキャナの状態が効果的に(もちろん、その時点においてスキャンしている箇所を維持したまま)リセットされることを意味しています。

3.8.3 スタート状態に関する注

以下において、スタート状態の使用に関する注をいくつか示します。

3.8.4 スタート状態を使う実例

プログラミングにおいて、何かをする方法を学ぶのに最良の方法は実際にそれをやってみることです。そのことを心にとめた上で、以下にスタート状態をどのように使うことができるかを示す実例を挙げます。

この実例は、様々な形式の日付をスキャンし、構成単位に分割します。例えばそれは以下のものを正しくスキャンし、日付の個々の部分を識別します。 ファイルの最初の部分では、月、および、日付の異なる部分に使われる数字形式を単に定義しています。この実例では、ある特定の方法でスキャン処理が進行するよう強制するためにスタート状態を使います。例えば、行の先頭で 1989 を見つければ、それが日と月の組み合わせではなく年であり、したがって、日付の次の部分が月に違いないことが分り、スキャン処理がそのとおりに進むよう強制します。このことにより、非常に単純な状態駆動のパーサを効果的に作成したことになり、日付をその構成要素にきれいに分割することができるようになります(このスキャナの内部で起こっていることをフロー・チャートに画いてみれば、このことが明瞭に見てとれるでしょう)。

このマニュアル中の他の実例と同様に、この実例も

を実行することにより手動でコンパイルすることができます。また、examples サブ・ディレクトリにおいて単に make dates を実行することでコンパイルすることもできます。


Copyright (C) 1992, 1993 Free Software Foundation

Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.

Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.

Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation.


日本語訳:市川和久
Japanese translation by Kazuhisa Ichikawa (ki@home.email.ne.jp)